package rtcsdp

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"gitee.com/general252/gortc/pkg/format"
	"github.com/pion/ice/v2"
	"github.com/pion/sdp/v3"
)

type MediaType string

const (
	MediaTypeVideo       MediaType = "video"
	MediaTypeAudio       MediaType = "audio"
	MediaTypeApplication MediaType = "application"
)

type MediaDirection string

const (
	DirectionSendRecv MediaDirection = "sendrecv"
	DirectionSendOnly MediaDirection = "sendonly"
	DirectionRecvOnly MediaDirection = "recvonly"
)

type Candidate struct {
	Type        string // host/srflx/prflx/relay
	NetworkType string // udp/tcp/udp4/udp6/tcp4/tcp6
	Address     string // 192.168.66.6
	Port        int    //

	Priority uint32

	RelAddr string
	RelPort int
}

type Media struct {
	Type         MediaType
	Direction    MediaDirection
	SSRC         uint32
	ReceiverSSRC uint32
	OverTCP      bool

	MID                       string // mid
	StreamId                  string
	TrackId                   string
	CName, StreamLabel, Label string

	RTCPMux   bool
	RTCPRsize bool

	Candidates []*Candidate

	// Formats contained into the media.
	Formats []format.Format
}

func (m *Media) Unmarshal(md *sdp.MediaDescription) error {
	c := m
	m.Formats = nil
	m.Type = MediaType(md.MediaName.Media)

	for _, payloadType := range md.MediaName.Formats {
		var (
			pt     uint8
			object mediaFMTP
		)

		if value, err := strconv.ParseUint(payloadType, 10, 8); err != nil {
			return err
		} else {
			pt = uint8(value)
		}

		fmtp, _ := object.Unmarshal(c.getAttribute(md.Attributes, pt, "fmtp"))
		rtpMap := c.getAttribute(md.Attributes, pt, "rtpmap")

		if f, err := format.Unmarshal(string(m.Type), pt, rtpMap, fmtp); err != nil {
			return err
		} else {
			m.Formats = append(m.Formats, f)
		}
	}

	if m.Formats == nil {
		return fmt.Errorf("no formats found")
	}

	if _, ok := md.Attribute(sdp.AttrKeyRTCPMux); ok {
		m.RTCPMux = true
	}
	if _, ok := md.Attribute(sdp.AttrKeyRTCPRsize); ok {
		m.RTCPRsize = true
	}

	for _, proto := range md.MediaName.Protos {
		if proto == "TCP" {
			m.OverTCP = true
		}
	}

	if v, ok := md.Attribute(sdp.AttrKeySSRC); ok {
		var value uint32
		if n, err := fmt.Sscanf(v, "%d", &value); err == nil && n == 1 {
			m.SSRC = value
		}
	}
	if v, ok := md.Attribute("bvssrc"); ok {
		var value uint32
		if n, err := fmt.Sscanf(v, "%d", &value); err == nil && n == 1 {
			m.ReceiverSSRC = value
		}
	}

	{
		var (
			ssrc                      uint32
			cname, streamLabel, label string
			streamId, trackId         string

			n   int
			err error
		)
		for _, attr := range md.Attributes {
			if attr.Key == sdp.AttrKeySSRC {
				if n, err = fmt.Sscanf(attr.Value, "%d cname:%s", &ssrc, &cname); err == nil && n == 2 {
					m.CName = cname
				} else if n, err = fmt.Sscanf(attr.Value, "%d mslabel:%s", &ssrc, &streamLabel); err == nil && n == 2 {
					m.StreamLabel = streamLabel
				} else if n, err = fmt.Sscanf(attr.Value, "%d label:%s", &ssrc, &label); err == nil && n == 2 {
					m.Label = label
				} else if n, err = fmt.Sscanf(attr.Value, "%d msid:%s %s", &ssrc, &streamId, &trackId); err == nil && n == 3 {
					m.StreamId = streamId
					m.TrackId = trackId
				}
			}
		}

		if value, ok := md.Attribute(sdp.AttrKeyMsid); ok {
			if parts := strings.SplitN(value, " ", 2); len(parts) == 2 {
				m.StreamId = parts[0]
				m.TrackId = parts[1]
			}
		}

		if v, ok := md.Attribute(sdp.AttrKeyMID); ok {
			m.MID = v
		}
	}

	for _, attr := range md.Attributes {
		if !attr.IsICECandidate() {
			continue
		}

		if value, err := ice.UnmarshalCandidate(attr.Value); err == nil {
			item := &Candidate{
				Type:        value.Type().String(),
				NetworkType: value.NetworkType().String(),
				Address:     value.Address(),
				Port:        value.Port(),
				Priority:    value.Priority(),
				RelAddr:     "",
				RelPort:     0,
			}
			if value.RelatedAddress() != nil {
				item.RelAddr = value.RelatedAddress().Address
				item.Port = value.RelatedAddress().Port
			}
			m.Candidates = append(m.Candidates, item)
		}
	}

	if _, ok := md.Attribute(sdp.AttrKeySendRecv); ok {
		m.Direction = DirectionSendRecv
	} else if _, ok = md.Attribute(sdp.AttrKeySendOnly); ok {
		m.Direction = DirectionSendOnly
	} else if _, ok = md.Attribute(sdp.AttrKeyRecvOnly); ok {
		m.Direction = DirectionRecvOnly
	}

	return nil
}

func (m Media) Marshal() *sdp.MediaDescription {
	md := &sdp.MediaDescription{
		MediaName: sdp.MediaName{
			Media:  string(m.Type),
			Protos: []string{"RTP", "AVP"}, // 	"UDP", "RTP", "AVP", "SAVP", "SAVPF", "MP2T", "TLS", "DTLS", "SCTP", "AVPF", "TCP",
		},
	}

	if m.OverTCP {
		md.MediaName.Protos = append(md.MediaName.Protos, "TCP")
	}

	if len(m.MID) > 0 {
		md.WithValueAttribute(sdp.AttrKeyMID, m.MID)
	}

	if m.RTCPMux {
		md.WithPropertyAttribute(sdp.AttrKeyRTCPMux)
	}
	if m.RTCPRsize {
		md.WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
	}

	for _, f := range m.Formats {
		var (
			pt           = f.PayloadType()
			objectRtpMap mediaRTPMap
			objectFMTP   mediaFMTP
		)

		objectRtpMap.Unmarshal(f.RTPMap())
		fmtp, _ := objectFMTP.Marshal(f.FMTP())

		md.WithCodec(pt, objectRtpMap.codec, objectRtpMap.clockRate, objectRtpMap.channels, fmtp)
	}

	md.WithMediaSource(m.SSRC, m.CName, m.StreamLabel, m.Label)

	if len(m.StreamId) > 0 && len(m.TrackId) > 0 {
		md.WithValueAttribute(sdp.AttrKeyMsid, fmt.Sprintf("%s %s", m.StreamId, m.TrackId))
	}

	{
		var candidates []ice.Candidate
		for _, value := range m.Candidates {
			switch value.Type {
			case "host":
				_ = ice.CandidateTypeHost
				if v, err := ice.NewCandidateHost(&ice.CandidateHostConfig{
					Network:   value.NetworkType,
					Address:   value.Address,
					Port:      value.Port,
					Component: 1,
					Priority:  value.Priority,
				}); err == nil {
					candidates = append(candidates, v)
				}
			case "srflx":
				if v, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
					Network:   value.NetworkType,
					Address:   value.Address,
					Port:      value.Port,
					Component: 1,
					Priority:  value.Priority,
					RelAddr:   value.RelAddr,
					RelPort:   value.RelPort,
				}); err == nil {
					candidates = append(candidates, v)
				}
			case "prflx":
				if v, err := ice.NewCandidatePeerReflexive(&ice.CandidatePeerReflexiveConfig{
					Network:   value.NetworkType,
					Address:   value.Address,
					Port:      value.Port,
					Component: 1,
					Priority:  value.Priority,
					RelAddr:   value.RelAddr,
					RelPort:   value.RelPort,
				}); err == nil {
					candidates = append(candidates, v)
				}
			case "relay":
				if v, err := ice.NewCandidateRelay(&ice.CandidateRelayConfig{
					Network:   value.NetworkType,
					Address:   value.Address,
					Port:      value.Port,
					Component: 1,
					Priority:  value.Priority,
					RelAddr:   value.RelAddr,
					RelPort:   value.RelPort,
				}); err == nil {
					candidates = append(candidates, v)
				}
			}
		}

		for _, candidate := range candidates {
			md.WithValueAttribute("candidate", candidate.Marshal())
		}
	}

	if len(string(m.Direction)) > 0 {
		md.WithPropertyAttribute(string(m.Direction))
	}

	return md
}

func (*Media) getAttribute(attributes []sdp.Attribute, payloadType uint8, key string) string {
	for _, attr := range attributes {
		if attr.Key == key {
			v := strings.TrimSpace(attr.Value)
			if parts := strings.SplitN(v, " ", 2); len(parts) == 2 {
				if tmp, err := strconv.ParseUint(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType {
					return parts[1]
				}
			}
		}
	}
	return ""
}

type mediaFMTP string

func (mediaFMTP) Unmarshal(data string) (map[string]string, bool) {
	if len(data) == 0 {
		return nil, false
	}

	ret := make(map[string]string)

	for _, kv := range strings.Split(data, ";") {
		kv = strings.Trim(kv, " ")

		if len(kv) == 0 {
			continue
		}

		tmp := strings.SplitN(kv, "=", 2)
		if len(tmp) != 2 {
			continue
		}

		ret[strings.ToLower(tmp[0])] = tmp[1]
	}

	return ret, true
}

func (mediaFMTP) Marshal(fmtp map[string]string) (string, bool) {
	if fmtp == nil || len(fmtp) == 0 {
		return "", false
	}

	type KV struct {
		key   string
		value string
	}

	var values []KV
	for k, v := range fmtp {
		values = append(values, KV{
			key:   k,
			value: v,
		})
	}

	sort.Slice(values, func(i, j int) bool {
		return values[i].key < values[j].key
	})

	parts := make([]string, 0, len(values))
	for _, value := range values {
		parts = append(parts, fmt.Sprintf("%s=%s", value.key, value.value))
	}

	return strings.Join(parts, "; "), true
}

type mediaRTPMap struct {
	codec     string
	clockRate uint32
	channels  uint16
}

func (c *mediaRTPMap) Unmarshal(data string) {
	parts := strings.Split(data, "/")
	if len(parts) >= 2 {
		c.codec = parts[0]
		if v, err := strconv.Atoi(parts[1]); err == nil {
			c.clockRate = uint32(v)
		}
		if len(parts) >= 3 {
			if v, err := strconv.Atoi(parts[2]); err == nil {
				c.channels = uint16(v)
			}
		}
	}
}

func (c *mediaRTPMap) Marshal() string {
	if c.channels == 0 {
		return fmt.Sprintf("%v/%v", c.codec, c.clockRate)
	} else {
		return fmt.Sprintf("%v/%v/%v", c.codec, c.clockRate, c.channels)
	}
}
